#include <debug.h>
#include <inttypes.h>
#include <round.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <bitmap.h>

#include "hash.h"
#include "userprog/gdt.h"
#include "userprog/pagedir.h"
#include "userprog/tss.h"
#include "userprog/syscall.h"
#include "filesys/directory.h"
#include "filesys/file.h"
#include "filesys/filesys.h"
#include "threads/flags.h"
#include "threads/init.h"
#include "threads/interrupt.h"
#include "threads/palloc.h"
#include "threads/thread.h"
#include "threads/synch.h"
#include "threads/vaddr.h"
#include "threads/malloc.h"

#include "vm/frame.h"
#include "vm/swap.h"
#include "vm/page.h"
#include "vm/mmap.h"

bool mmap_init(void){
	struct thread *cur = thread_current();

	cur->mmaps = (struct hash *) malloc (sizeof(struct hash));
	if(cur->mmaps == NULL)
		return false;

	bool success = hash_init (cur->mmaps, mmap_hash, mmap_less, NULL);
	if (success){
		sema_init (&mmap_sema, 1);
		return true;
	}
	else
	{
		free (cur->mmaps);
		return false;
	}
}

int syscall_mmap(int fd, void *addr){


	struct file *file;
	struct mmap *_m;
	
	struct page _iter;
	struct page *_p;
	struct hash_elem *p_elem, *m_elem;
	struct thread *cur = thread_current();

	int32_t len,len_copy;
	void *uaddr_iter;

	int32_t offset;
	bool ret;




	// detection started!
	if(fd < 2)
		return -1;
	if(addr == 0 || (uint32_t)addr % PGSIZE != 0)
		return -1;
	
	file = get_file_from_thread( fd );
	if(file == NULL)
		return -1;

	len = file_length( file );
	if(len == 0)
		return -1;

	_m = (struct mmap *)malloc(sizeof(struct mmap));
	if(_m == NULL)
		return -1;

	_m->uaddr = addr;
	_m->mmap_id = fd;


	// basic errors are detected from this moment
	
	lock_acquire(&cur->page_lock);


	// check if there any pages are in that frame
	len_copy = len;
	uaddr_iter = addr;
	p_elem = NULL;

	while (len_copy > 0) 
    {
		_iter.upage = uaddr_iter;
		p_elem = hash_find (cur->pages, &_iter.hash_elem);
		
		if(p_elem != NULL){
			free(_m);
			lock_release(&cur->page_lock);
			return -1;
		}
		len_copy -= PGSIZE;
		uaddr_iter += PGSIZE;
    }


	m_elem =hash_insert(cur->mmaps,&_m->hash_elem);
	if ( m_elem!=NULL)
	{
		free(_m);
		lock_release(&cur->page_lock);
		return -1;
	}




	// all impossible cases are detected from this moment, so just do lazy loading from here
	// lock is still holded
	// do the lazy loading for all pages

	uaddr_iter = addr;
	p_elem = NULL;
	offset = 0;
	while (len > 0) 
    {
		size_t page_read_bytes = len < PGSIZE ? len : PGSIZE;

		
	//	lazy_page_insert
		_p = (struct page *)malloc(sizeof(struct page));
		if(_p == NULL){
			free(_m);
			lock_release(&cur->page_lock);
			return -1;
		}

		ret = true;
				
		_p->upage = uaddr_iter;
		_p->t = cur;
		_p->swap_position = -1;
		_p->writable = true;
		_p->fp = file;
		_p->offset = offset;
		_p->readbyte = page_read_bytes;
		_p->isLoaded = false;

		struct hash_elem *h_elem = hash_find(cur ->pages, &_p->hash_elem);
		if(h_elem != NULL)
		{
			ret=false;
			free(_p);
		}
		else{
			h_elem =hash_insert(cur->pages,&_p->hash_elem);
			if ( h_elem!=NULL)
			{
				ret=false;
				free(_p);
			}
		}

		if(!ret){
			free(_m);
			lock_release(&cur->page_lock);
			return -1;
		}

		len -= page_read_bytes;
		uaddr_iter += PGSIZE;
		offset += page_read_bytes;
    }

	lock_release(&cur->page_lock);
	
	return fd;
}


bool syscall_munmap(int mmap_id){
	
	struct mmap checker;
	struct page finder;
	struct hash_elem *m_elem, *p_elem;
	struct mmap *_m;
	struct page *_p;
	struct thread *cur = thread_current();
	struct file *file;
	int32_t len;
	void *uaddr_iter;
	void *temp;
	int pos;
	int off_t, off_iter;

	checker.mmap_id = mmap_id;
	m_elem = hash_find (cur->mmaps, &checker.hash_elem);
		
	if(m_elem == NULL){
		return false;
	}
	
	_m = hash_entry(m_elem, struct mmap, hash_elem);
	file = get_file_from_thread( _m->mmap_id );
//	file = get_file_from_thread( _m->fd );
	if(file == NULL)
		return false;

	len = file_length( file );
	uaddr_iter = _m->uaddr;
	
	if(len<0)
		return false;
	
	temp = palloc_get_page (0);
	if(temp == NULL)
		return false;



	

//	lock_acquire(&cur->page_lock);
	lock_acquire(&frame_lock);
	lock_acquire(&swap_lock);

	
	off_t = file_tell (file);
	off_iter = 0;
	file_seek (file, 0);
	
	while (len > 0) 
    {
		// find laze loaded page
		finder.upage = uaddr_iter;
		p_elem = hash_find (cur->pages, &finder.hash_elem);
		// if failed to find, then return false
		if(p_elem == NULL){
			file_seek (file,off_t);
//			lock_release(&cur->page_lock);
			lock_release(&swap_lock);
			lock_release(&frame_lock);

			palloc_free_page(temp);

			return false;
		}
		_p = hash_entry(p_elem, struct page, hash_elem);
		size_t page_read_bytes = _p->readbyte;

		// check whether it's in frame or not

		if ( pagedir_is_dirty ( _p->t->pagedir, _p->upage) ){
			memset( temp, 0, PGSIZE);

			void *kpage = pagedir_get_page(_p->t->pagedir, _p->upage);
			if( kpage != NULL){
				memcpy( temp, kpage, page_read_bytes);
				
				lock_acquire(&file_lock);
//				printf("[[%s] %d]\n",temp, page_read_bytes);
				file_seek(file, off_iter);

				file_write(file, temp, page_read_bytes);

				lock_release(&file_lock);
			}

			else if(_p->swap_position!=-1){
//				printf("123123\n");
				pos = _p->swap_position;
				
				int i ;	
				for ( i = 0 ; i < FACTOR ; ++i)
				{
					disk_read(swap_disk,pos*FACTOR+i,temp+i*DISK_SECTOR_SIZE);
				}
				bitmap_set(swap_bitmap,pos,false);
				_p->swap_position = -1;
				
				lock_acquire(&file_lock);

				file_seek(file, off_iter);
				file_write(file, temp, page_read_bytes); 

				lock_release(&file_lock);
//				printf("[[%s] %d]\n",temp, off_iter);
			}
			else{
				// not loaded.. if loaded then an error occur
				if(_p->isLoaded){
					file_seek (file,off_t);
			
					lock_release(&swap_lock);
					lock_release(&frame_lock);

					palloc_free_page(temp);
					return false;
				}
			}
		}

		hash_delete(cur->pages, p_elem);
		free(_p);

		len -= page_read_bytes;
		uaddr_iter += PGSIZE;
		off_iter += page_read_bytes;
    }
	
	file_seek (file,off_t);

	lock_release(&swap_lock);
	lock_release(&frame_lock);

	palloc_free_page(temp);

	return true;
}



void mmap_deallocate(struct thread *t)
{
//	lock_acquire(&t->mmap_lock);
		hash_destroy(t->mmaps,mmap_destructor);
//	lock_release(&t->mmap_lock);

	free(t->mmaps);
}

void mmap_destructor(struct hash_elem *h_elem,void *aux UNUSED )
{
	struct mmap *_p= hash_entry(h_elem, struct mmap,hash_elem);
	// neet to write some code for file write!!
	

	syscall_munmap( _p->mmap_id );
/*
	if(_p->swap_position!=-1){
		swap_remove_page(_p);
	}
*/
	free(_p);
}

/* Returns a hash value for mmap p. */
unsigned
mmap_hash (const struct hash_elem *p_, void *aux UNUSED)
{
	const struct mmap *p = hash_entry (p_, struct mmap, hash_elem);

	// ?????????????????????????????????????????????????????????????????????
	return hash_bytes (&p->mmap_id, sizeof p->mmap_id);
}

/* Returns true if mmap a precedes mmap b. */
bool
mmap_less (const struct hash_elem *a_, const struct hash_elem *b_,
			void *aux UNUSED)
{
	const struct mmap *a = hash_entry (a_, struct mmap, hash_elem);
	const struct mmap *b = hash_entry (b_, struct mmap, hash_elem);
	return a->mmap_id < b->mmap_id;
}

/* Returns the mmap containing the given virtual address,
 * or a null pointer if no such mmap exists. */
struct mmap *
mmap_lookup (int mmap_id, struct thread *t)
{
	if(t==NULL)
		t = thread_current();
        
	struct mmap p;
	struct hash_elem *e;

//	lock_acquire(&t->mmap_lock);

	p.mmap_id = mmap_id;
	e = hash_find (t->mmaps, &p.hash_elem);

//	lock_release(&t->mmap_lock);

	return e != NULL ? hash_entry (e, struct mmap, hash_elem) : NULL;
}
